{
 "cells": [
  {
   "cell_type": "code",
   "id": "fa6cab64",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-08-26T01:43:00.711262Z",
     "start_time": "2025-08-26T01:43:00.704686Z"
    }
   },
   "source": [
    "# 导入搜索环境的api-key\n",
    "import os\n",
    "\n",
    "with open(\"api/search_api_key\", \"r\") as f:\n",
    "    search_api_key = f.read()\n",
    "    \n",
    "os.environ[\"TAVILY_API_KEY\"] = search_api_key"
   ],
   "outputs": [],
   "execution_count": 5
  },
  {
   "cell_type": "code",
   "id": "e9acda87",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-08-26T01:43:02.630381Z",
     "start_time": "2025-08-26T01:43:02.623610Z"
    }
   },
   "source": [
    "from langchain_tavily import TavilySearch\n",
    "\n",
    "tool = TavilySearch(max_results=2)\n",
    "tools = [tool]\n",
    "# tool.invoke(\"LangGraph 中的“节点”是什么？\")"
   ],
   "outputs": [],
   "execution_count": 6
  },
  {
   "cell_type": "code",
   "id": "4abcf4ee",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-08-26T01:43:05.399581Z",
     "start_time": "2025-08-26T01:43:04.939415Z"
    }
   },
   "source": [
    "# langgraph工作流构建大模型\n",
    "import os\n",
    "\n",
    "from langchain.chat_models.tongyi import ChatTongyi\n",
    "\n",
    "with open(\"api/api_key.txt\", \"r\") as f:\n",
    "    api_key = f.read()\n",
    "\n",
    "os.environ[\"DASHSCOPE_API_KEY\"] = api_key\n",
    "\n",
    "llm = ChatTongyi(model_name=\"qwen-plus\")\n",
    "\n",
    "from typing import Annotated\n",
    "\n",
    "from typing_extensions import TypedDict\n",
    "\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "from langgraph.graph.message import add_messages\n",
    "\n",
    "class State(TypedDict):\n",
    "    messages: Annotated[list, add_messages]\n",
    "\n",
    "graph_builder = StateGraph(State)\n",
    "\n",
    "# 将工具绑定到大模型上面\n",
    "llm_with_tools = llm.bind_tools(tools)\n",
    "\n",
    "def chatbot(state: State):\n",
    "    return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
    "\n",
    "# 绑定一个执行的函数结点\n",
    "graph_builder.add_node(\"chatbot\", chatbot)\n",
    "\n",
    "import json\n",
    "\n",
    "from langchain_core.messages import ToolMessage\n",
    "\n",
    "class BasicToolNode:\n",
    "    def __init__(self, tools:list) -> None:\n",
    "        # 构建基础的工具名称\n",
    "        self.tools_by_name = {tool.name: tool for tool in tools}\n",
    "\n",
    "    def __call__(self, inputs: dict):\n",
    "        if messages := inputs.get(\"messages\", []):\n",
    "            # 最新回复中可能不只是自然语音回答还可能包含工具调用请求\n",
    "            message = messages[-1]\n",
    "        else:\n",
    "            raise ValueError(\"没有messages数据\")\n",
    "        print(\"message.tool_calls:\", message.tool_calls)\n",
    "        outputs = []\n",
    "\n",
    "        for tool_call in message.tool_calls:\n",
    "            tool_result = self.tools_by_name[tool_call[\"name\"]].invoke(tool_call[\"args\"])\n",
    "            outputs.append(\n",
    "                ToolMessage(\n",
    "                    content=json.dumps(tool_result),\n",
    "                    name=tool_call[\"name\"],\n",
    "                    tool_call_id=tool_call[\"id\"],\n",
    "                )\n",
    "            )\n",
    "        return {\"messages\": outputs}\n",
    "\n",
    "tool_node = BasicToolNode(tools=[tool])\n",
    "graph_builder.add_node(\"tools\", tool_node)"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<langgraph.graph.state.StateGraph at 0x1ddc6270550>"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 7
  },
  {
   "cell_type": "code",
   "id": "fd844a67-c17d-410a-8a8a-99ebe470f424",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-08-26T01:43:15.487872Z",
     "start_time": "2025-08-26T01:43:15.475755Z"
    }
   },
   "source": [
    "def route_tools(state: State):\n",
    "    if isinstance(state, list):\n",
    "        ai_message = state[-1]\n",
    "    elif messages := state.get(\"messages\", []):\n",
    "        ai_message = messages[-1]\n",
    "    else:\n",
    "        raise ValueError(f\"No messages found in input state to tool_edge: {state}\")\n",
    "    if hasattr(ai_message, \"tool_calls\") and len(ai_message.tool_calls) > 0:\n",
    "        return \"tools\"\n",
    "    return END\n",
    "\n",
    "graph_builder.add_conditional_edges(\n",
    "    \"chatbot\",\n",
    "    route_tools,\n",
    "    {\"tools\": \"tools\", END:END}\n",
    ")\n",
    "\n",
    "graph_builder.add_edge(START, \"chatbot\")\n",
    "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
    "graph = graph_builder.compile()"
   ],
   "outputs": [],
   "execution_count": 8
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "fa6e32e2-0641-4fb4-84c7-29358029623c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdin",
     "output_type": "stream",
     "text": [
      "User:  我想了解一下贵州省信息与计算科学重点实验室怎么样\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'chatbot': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_7af4fb406133414bb69262', 'type': 'function', 'function': {'name': 'tavily_search', 'arguments': '{\"query\": \"贵州省信息与计算科学重点实验室怎么样\"}'}}]}, response_metadata={'model_name': 'qwen-plus', 'finish_reason': 'tool_calls', 'request_id': 'caf460e6-d2c0-91da-8a83-aacfc1eca601', 'token_usage': {'input_tokens': 1812, 'output_tokens': 28, 'total_tokens': 1840, 'prompt_tokens_details': {'cached_tokens': 0}}}, id='run--a20f2e2b-0dec-4a0e-90e9-53194287844b-0', tool_calls=[{'name': 'tavily_search', 'args': {'query': '贵州省信息与计算科学重点实验室怎么样'}, 'id': 'call_7af4fb406133414bb69262', 'type': 'tool_call'}])]}}\n",
      "{'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_7af4fb406133414bb69262', 'type': 'function', 'function': {'name': 'tavily_search', 'arguments': '{\"query\": \"贵州省信息与计算科学重点实验室怎么样\"}'}}]}, response_metadata={'model_name': 'qwen-plus', 'finish_reason': 'tool_calls', 'request_id': 'caf460e6-d2c0-91da-8a83-aacfc1eca601', 'token_usage': {'input_tokens': 1812, 'output_tokens': 28, 'total_tokens': 1840, 'prompt_tokens_details': {'cached_tokens': 0}}}, id='run--a20f2e2b-0dec-4a0e-90e9-53194287844b-0', tool_calls=[{'name': 'tavily_search', 'args': {'query': '贵州省信息与计算科学重点实验室怎么样'}, 'id': 'call_7af4fb406133414bb69262', 'type': 'tool_call'}])]}\n",
      "Assistant: \n",
      "message.tool_calls: [{'name': 'tavily_search', 'args': {'query': '贵州省信息与计算科学重点实验室怎么样'}, 'id': 'call_7af4fb406133414bb69262', 'type': 'tool_call'}]\n",
      "{'tools': {'messages': [ToolMessage(content='{\"query\": \"\\\\u8d35\\\\u5dde\\\\u7701\\\\u4fe1\\\\u606f\\\\u4e0e\\\\u8ba1\\\\u7b97\\\\u79d1\\\\u5b66\\\\u91cd\\\\u70b9\\\\u5b9e\\\\u9a8c\\\\u5ba4\\\\u600e\\\\u4e48\\\\u6837\", \"follow_up_questions\": null, \"answer\": null, \"images\": [], \"results\": [{\"url\": \"https://ics.gznu.edu.cn/\", \"title\": \"\\\\u8d35\\\\u5dde\\\\u7701\\\\u4fe1\\\\u606f\\\\u4e0e\\\\u8ba1\\\\u7b97\\\\u79d1\\\\u5b66\\\\u91cd\\\\u70b9\\\\u5b9e\\\\u9a8c\\\\u5ba4\", \"content\": \"\\\\u9996\\\\u9875, |, \\\\u5b9e\\\\u9a8c\\\\u5ba4\\\\u6982\\\\u51b5, |, \\\\u5b9e\\\\u9a8c\\\\u5ba4\\\\u65b0\\\\u95fb, |, \\\\u7814\\\\u7a76\\\\u961f\\\\u4f0d, |, \\\\u79d1\\\\u5b66\\\\u7814\\\\u7a76, |, \\\\u4eba\\\\u624d\\\\u57f9\\\\u517b, |, \\\\u79d1\\\\u7814\\\\u6210\\\\u679c, |, \\\\u4e3b\\\\u8981\\\\u8bbe\\\\u5907, |, \\\\u7814\\\\u7a76\\\\u751f\\\\u6559\\\\u80b2, |, \\\\u5bfc\\\\u5e08\\\\u56e2\\\\u961f, |, \\\\u5b66\\\\u4e60\\\\u5e73\\\\u53f0\", \"score\": 0.98503, \"raw_content\": null}, {\"url\": \"https://www.zhihu.com/question/652408107\", \"title\": \"\\\\u60f3\\\\u95ee\\\\u4e00\\\\u4e0b\\\\u8d35\\\\u5dde\\\\u7701\\\\u4fe1\\\\u606f\\\\u4e0e\\\\u8ba1\\\\u7b97\\\\u79d1\\\\u5b66\\\\u91cd\\\\u70b9\\\\u5b9e\\\\u9a8c\\\\u5ba4\\\\u7684\\\\u7814\\\\u7a76\\\\u751f\\\\u600e\\\\u4e48\\\\u6837\\\\uff1f - \\\\u77e5\\\\u4e4e\", \"content\": \"\\\\u60f3\\\\u95ee\\\\u4e00\\\\u4e0b\\\\u8d35\\\\u5dde\\\\u7701\\\\u4fe1\\\\u606f\\\\u4e0e\\\\u8ba1\\\\u7b97\\\\u79d1\\\\u5b66\\\\u91cd\\\\u70b9\\\\u5b9e\\\\u9a8c\\\\u5ba4\\\\u7684\\\\u7814\\\\u7a76\\\\u751f\\\\u600e\\\\u4e48\\\\u6837\\\\uff1f \\\\u672c\\\\u4eba23\\\\u8003\\\\u7814\\\\u8c03\\\\u5242\\\\uff0c11408\\\\u603b\\\\u5206323\\\\uff0c\\\\u5728\\\\u627e\\\\u8c03\\\\u5242\\\\u7684\\\\u8fc7\\\\u7a0b\\\\u4e2d\\\\u53d1\\\\u73b0\\\\u8fd9\\\\u4e2a\\\\u5b9e\\\\u9a8c\\\\u5ba4\\\\u62db\\\\u7684\\\\u4eba\\\\u4e0d\\\\u5c11\\\\uff0c\\\\u60f3\\\\u95ee\\\\u4e0b\\\\u60c5\\\\u51b5.\", \"score\": 0.98468, \"raw_content\": null}], \"response_time\": 1.48, \"request_id\": \"0edaf00b-0bc8-43ed-bbd9-456b6e6f0f00\"}', name='tavily_search', id='bc773578-f983-4aec-aa9b-3e3c502d1d03', tool_call_id='call_7af4fb406133414bb69262')]}}\n",
      "{'messages': [ToolMessage(content='{\"query\": \"\\\\u8d35\\\\u5dde\\\\u7701\\\\u4fe1\\\\u606f\\\\u4e0e\\\\u8ba1\\\\u7b97\\\\u79d1\\\\u5b66\\\\u91cd\\\\u70b9\\\\u5b9e\\\\u9a8c\\\\u5ba4\\\\u600e\\\\u4e48\\\\u6837\", \"follow_up_questions\": null, \"answer\": null, \"images\": [], \"results\": [{\"url\": \"https://ics.gznu.edu.cn/\", \"title\": \"\\\\u8d35\\\\u5dde\\\\u7701\\\\u4fe1\\\\u606f\\\\u4e0e\\\\u8ba1\\\\u7b97\\\\u79d1\\\\u5b66\\\\u91cd\\\\u70b9\\\\u5b9e\\\\u9a8c\\\\u5ba4\", \"content\": \"\\\\u9996\\\\u9875, |, \\\\u5b9e\\\\u9a8c\\\\u5ba4\\\\u6982\\\\u51b5, |, \\\\u5b9e\\\\u9a8c\\\\u5ba4\\\\u65b0\\\\u95fb, |, \\\\u7814\\\\u7a76\\\\u961f\\\\u4f0d, |, \\\\u79d1\\\\u5b66\\\\u7814\\\\u7a76, |, \\\\u4eba\\\\u624d\\\\u57f9\\\\u517b, |, \\\\u79d1\\\\u7814\\\\u6210\\\\u679c, |, \\\\u4e3b\\\\u8981\\\\u8bbe\\\\u5907, |, \\\\u7814\\\\u7a76\\\\u751f\\\\u6559\\\\u80b2, |, \\\\u5bfc\\\\u5e08\\\\u56e2\\\\u961f, |, \\\\u5b66\\\\u4e60\\\\u5e73\\\\u53f0\", \"score\": 0.98503, \"raw_content\": null}, {\"url\": \"https://www.zhihu.com/question/652408107\", \"title\": \"\\\\u60f3\\\\u95ee\\\\u4e00\\\\u4e0b\\\\u8d35\\\\u5dde\\\\u7701\\\\u4fe1\\\\u606f\\\\u4e0e\\\\u8ba1\\\\u7b97\\\\u79d1\\\\u5b66\\\\u91cd\\\\u70b9\\\\u5b9e\\\\u9a8c\\\\u5ba4\\\\u7684\\\\u7814\\\\u7a76\\\\u751f\\\\u600e\\\\u4e48\\\\u6837\\\\uff1f - \\\\u77e5\\\\u4e4e\", \"content\": \"\\\\u60f3\\\\u95ee\\\\u4e00\\\\u4e0b\\\\u8d35\\\\u5dde\\\\u7701\\\\u4fe1\\\\u606f\\\\u4e0e\\\\u8ba1\\\\u7b97\\\\u79d1\\\\u5b66\\\\u91cd\\\\u70b9\\\\u5b9e\\\\u9a8c\\\\u5ba4\\\\u7684\\\\u7814\\\\u7a76\\\\u751f\\\\u600e\\\\u4e48\\\\u6837\\\\uff1f \\\\u672c\\\\u4eba23\\\\u8003\\\\u7814\\\\u8c03\\\\u5242\\\\uff0c11408\\\\u603b\\\\u5206323\\\\uff0c\\\\u5728\\\\u627e\\\\u8c03\\\\u5242\\\\u7684\\\\u8fc7\\\\u7a0b\\\\u4e2d\\\\u53d1\\\\u73b0\\\\u8fd9\\\\u4e2a\\\\u5b9e\\\\u9a8c\\\\u5ba4\\\\u62db\\\\u7684\\\\u4eba\\\\u4e0d\\\\u5c11\\\\uff0c\\\\u60f3\\\\u95ee\\\\u4e0b\\\\u60c5\\\\u51b5.\", \"score\": 0.98468, \"raw_content\": null}], \"response_time\": 1.48, \"request_id\": \"0edaf00b-0bc8-43ed-bbd9-456b6e6f0f00\"}', name='tavily_search', id='bc773578-f983-4aec-aa9b-3e3c502d1d03', tool_call_id='call_7af4fb406133414bb69262')]}\n",
      "Assistant: {\"query\": \"\\u8d35\\u5dde\\u7701\\u4fe1\\u606f\\u4e0e\\u8ba1\\u7b97\\u79d1\\u5b66\\u91cd\\u70b9\\u5b9e\\u9a8c\\u5ba4\\u600e\\u4e48\\u6837\", \"follow_up_questions\": null, \"answer\": null, \"images\": [], \"results\": [{\"url\": \"https://ics.gznu.edu.cn/\", \"title\": \"\\u8d35\\u5dde\\u7701\\u4fe1\\u606f\\u4e0e\\u8ba1\\u7b97\\u79d1\\u5b66\\u91cd\\u70b9\\u5b9e\\u9a8c\\u5ba4\", \"content\": \"\\u9996\\u9875, |, \\u5b9e\\u9a8c\\u5ba4\\u6982\\u51b5, |, \\u5b9e\\u9a8c\\u5ba4\\u65b0\\u95fb, |, \\u7814\\u7a76\\u961f\\u4f0d, |, \\u79d1\\u5b66\\u7814\\u7a76, |, \\u4eba\\u624d\\u57f9\\u517b, |, \\u79d1\\u7814\\u6210\\u679c, |, \\u4e3b\\u8981\\u8bbe\\u5907, |, \\u7814\\u7a76\\u751f\\u6559\\u80b2, |, \\u5bfc\\u5e08\\u56e2\\u961f, |, \\u5b66\\u4e60\\u5e73\\u53f0\", \"score\": 0.98503, \"raw_content\": null}, {\"url\": \"https://www.zhihu.com/question/652408107\", \"title\": \"\\u60f3\\u95ee\\u4e00\\u4e0b\\u8d35\\u5dde\\u7701\\u4fe1\\u606f\\u4e0e\\u8ba1\\u7b97\\u79d1\\u5b66\\u91cd\\u70b9\\u5b9e\\u9a8c\\u5ba4\\u7684\\u7814\\u7a76\\u751f\\u600e\\u4e48\\u6837\\uff1f - \\u77e5\\u4e4e\", \"content\": \"\\u60f3\\u95ee\\u4e00\\u4e0b\\u8d35\\u5dde\\u7701\\u4fe1\\u606f\\u4e0e\\u8ba1\\u7b97\\u79d1\\u5b66\\u91cd\\u70b9\\u5b9e\\u9a8c\\u5ba4\\u7684\\u7814\\u7a76\\u751f\\u600e\\u4e48\\u6837\\uff1f \\u672c\\u4eba23\\u8003\\u7814\\u8c03\\u5242\\uff0c11408\\u603b\\u5206323\\uff0c\\u5728\\u627e\\u8c03\\u5242\\u7684\\u8fc7\\u7a0b\\u4e2d\\u53d1\\u73b0\\u8fd9\\u4e2a\\u5b9e\\u9a8c\\u5ba4\\u62db\\u7684\\u4eba\\u4e0d\\u5c11\\uff0c\\u60f3\\u95ee\\u4e0b\\u60c5\\u51b5.\", \"score\": 0.98468, \"raw_content\": null}], \"response_time\": 1.48, \"request_id\": \"0edaf00b-0bc8-43ed-bbd9-456b6e6f0f00\"}\n",
      "{'chatbot': {'messages': [AIMessage(content='贵州省信息与计算科学重点实验室是贵州师范大学的一个重要研究机构，主要聚焦于信息科学与计算科学领域的研究。以下是一些关键信息：\\n\\n1. **实验室概况**  \\n   实验室的官网（[https://ics.gznu.edu.cn/](https://ics.gznu.edu.cn/)）介绍了其基本情况，包括实验室新闻、研究团队、科学研究、人才培养、科研成果、主要设备等。\\n\\n2. **研究方向**  \\n   实验室的研究方向包括大数据分析、人工智能、计算数学、信息处理等领域。其研究团队由多位知名教授和研究人员组成，致力于推动相关领域的技术进步和应用创新。\\n\\n3. **人才培养**  \\n   实验室注重人才培养，提供研究生教育平台，并与国内外多所高校和研究机构开展合作交流。通过科研项目和实践平台，学生能够接触到前沿的科学技术。\\n\\n4. **科研成果**  \\n   实验室在多个领域取得了显著的科研成果，包括发表高水平论文、申请专利以及参与国家级和省级科研项目。\\n\\n5. **研究生教育**  \\n   根据知乎上的一篇相关提问（[https://www.zhihu.com/question/652408107](https://www.zhihu.com/question/652408107)），实验室对研究生的培养也受到一定关注。有考生提到，该实验室招收的学生人数较多，适合希望在信息与计算科学领域深入研究的学生。\\n\\n总体来说，贵州省信息与计算科学重点实验室是一个具有较强科研实力和人才培养能力的机构，对于有兴趣在信息与计算科学领域发展的学生和研究人员来说，是一个值得考虑的选择。', additional_kwargs={}, response_metadata={'model_name': 'qwen-plus', 'finish_reason': 'stop', 'request_id': '7002db8a-cdde-9273-9cd3-b0efb1d1eb06', 'token_usage': {'input_tokens': 2864, 'output_tokens': 344, 'total_tokens': 3208, 'prompt_tokens_details': {'cached_tokens': 0}}}, id='run--55170262-3bd4-4c0a-8ee6-df077f5beecc-0')]}}\n",
      "{'messages': [AIMessage(content='贵州省信息与计算科学重点实验室是贵州师范大学的一个重要研究机构，主要聚焦于信息科学与计算科学领域的研究。以下是一些关键信息：\\n\\n1. **实验室概况**  \\n   实验室的官网（[https://ics.gznu.edu.cn/](https://ics.gznu.edu.cn/)）介绍了其基本情况，包括实验室新闻、研究团队、科学研究、人才培养、科研成果、主要设备等。\\n\\n2. **研究方向**  \\n   实验室的研究方向包括大数据分析、人工智能、计算数学、信息处理等领域。其研究团队由多位知名教授和研究人员组成，致力于推动相关领域的技术进步和应用创新。\\n\\n3. **人才培养**  \\n   实验室注重人才培养，提供研究生教育平台，并与国内外多所高校和研究机构开展合作交流。通过科研项目和实践平台，学生能够接触到前沿的科学技术。\\n\\n4. **科研成果**  \\n   实验室在多个领域取得了显著的科研成果，包括发表高水平论文、申请专利以及参与国家级和省级科研项目。\\n\\n5. **研究生教育**  \\n   根据知乎上的一篇相关提问（[https://www.zhihu.com/question/652408107](https://www.zhihu.com/question/652408107)），实验室对研究生的培养也受到一定关注。有考生提到，该实验室招收的学生人数较多，适合希望在信息与计算科学领域深入研究的学生。\\n\\n总体来说，贵州省信息与计算科学重点实验室是一个具有较强科研实力和人才培养能力的机构，对于有兴趣在信息与计算科学领域发展的学生和研究人员来说，是一个值得考虑的选择。', additional_kwargs={}, response_metadata={'model_name': 'qwen-plus', 'finish_reason': 'stop', 'request_id': '7002db8a-cdde-9273-9cd3-b0efb1d1eb06', 'token_usage': {'input_tokens': 2864, 'output_tokens': 344, 'total_tokens': 3208, 'prompt_tokens_details': {'cached_tokens': 0}}}, id='run--55170262-3bd4-4c0a-8ee6-df077f5beecc-0')]}\n",
      "Assistant: 贵州省信息与计算科学重点实验室是贵州师范大学的一个重要研究机构，主要聚焦于信息科学与计算科学领域的研究。以下是一些关键信息：\n",
      "\n",
      "1. **实验室概况**  \n",
      "   实验室的官网（[https://ics.gznu.edu.cn/](https://ics.gznu.edu.cn/)）介绍了其基本情况，包括实验室新闻、研究团队、科学研究、人才培养、科研成果、主要设备等。\n",
      "\n",
      "2. **研究方向**  \n",
      "   实验室的研究方向包括大数据分析、人工智能、计算数学、信息处理等领域。其研究团队由多位知名教授和研究人员组成，致力于推动相关领域的技术进步和应用创新。\n",
      "\n",
      "3. **人才培养**  \n",
      "   实验室注重人才培养，提供研究生教育平台，并与国内外多所高校和研究机构开展合作交流。通过科研项目和实践平台，学生能够接触到前沿的科学技术。\n",
      "\n",
      "4. **科研成果**  \n",
      "   实验室在多个领域取得了显著的科研成果，包括发表高水平论文、申请专利以及参与国家级和省级科研项目。\n",
      "\n",
      "5. **研究生教育**  \n",
      "   根据知乎上的一篇相关提问（[https://www.zhihu.com/question/652408107](https://www.zhihu.com/question/652408107)），实验室对研究生的培养也受到一定关注。有考生提到，该实验室招收的学生人数较多，适合希望在信息与计算科学领域深入研究的学生。\n",
      "\n",
      "总体来说，贵州省信息与计算科学重点实验室是一个具有较强科研实力和人才培养能力的机构，对于有兴趣在信息与计算科学领域发展的学生和研究人员来说，是一个值得考虑的选择。\n"
     ]
    },
    {
     "ename": "KeyboardInterrupt",
     "evalue": "Interrupted by user",
     "output_type": "error",
     "traceback": [
      "\u001B[1;31m---------------------------------------------------------------------------\u001B[0m",
      "\u001B[1;31mKeyboardInterrupt\u001B[0m                         Traceback (most recent call last)",
      "Cell \u001B[1;32mIn[12], line 10\u001B[0m\n\u001B[0;32m      8\u001B[0m \u001B[38;5;28;01mwhile\u001B[39;00m \u001B[38;5;28;01mTrue\u001B[39;00m:\n\u001B[0;32m      9\u001B[0m     \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[1;32m---> 10\u001B[0m         user_input \u001B[38;5;241m=\u001B[39m \u001B[38;5;28minput\u001B[39m(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mUser: \u001B[39m\u001B[38;5;124m\"\u001B[39m)\n\u001B[0;32m     11\u001B[0m         \u001B[38;5;28;01mif\u001B[39;00m user_input\u001B[38;5;241m.\u001B[39mlower() \u001B[38;5;129;01min\u001B[39;00m [\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mquit\u001B[39m\u001B[38;5;124m\"\u001B[39m, \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mexit\u001B[39m\u001B[38;5;124m\"\u001B[39m, \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mq\u001B[39m\u001B[38;5;124m\"\u001B[39m]:\n\u001B[0;32m     12\u001B[0m             \u001B[38;5;28mprint\u001B[39m(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mGoodbye!\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n",
      "File \u001B[1;32mD:\\anaconda\\Lib\\site-packages\\ipykernel\\kernelbase.py:1262\u001B[0m, in \u001B[0;36mKernel.raw_input\u001B[1;34m(self, prompt)\u001B[0m\n\u001B[0;32m   1260\u001B[0m     msg \u001B[38;5;241m=\u001B[39m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mraw_input was called, but this frontend does not support input requests.\u001B[39m\u001B[38;5;124m\"\u001B[39m\n\u001B[0;32m   1261\u001B[0m     \u001B[38;5;28;01mraise\u001B[39;00m StdinNotImplementedError(msg)\n\u001B[1;32m-> 1262\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_input_request(\n\u001B[0;32m   1263\u001B[0m     \u001B[38;5;28mstr\u001B[39m(prompt),\n\u001B[0;32m   1264\u001B[0m     \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_parent_ident[\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mshell\u001B[39m\u001B[38;5;124m\"\u001B[39m],\n\u001B[0;32m   1265\u001B[0m     \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mget_parent(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mshell\u001B[39m\u001B[38;5;124m\"\u001B[39m),\n\u001B[0;32m   1266\u001B[0m     password\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mFalse\u001B[39;00m,\n\u001B[0;32m   1267\u001B[0m )\n",
      "File \u001B[1;32mD:\\anaconda\\Lib\\site-packages\\ipykernel\\kernelbase.py:1305\u001B[0m, in \u001B[0;36mKernel._input_request\u001B[1;34m(self, prompt, ident, parent, password)\u001B[0m\n\u001B[0;32m   1302\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m \u001B[38;5;167;01mKeyboardInterrupt\u001B[39;00m:\n\u001B[0;32m   1303\u001B[0m     \u001B[38;5;66;03m# re-raise KeyboardInterrupt, to truncate traceback\u001B[39;00m\n\u001B[0;32m   1304\u001B[0m     msg \u001B[38;5;241m=\u001B[39m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mInterrupted by user\u001B[39m\u001B[38;5;124m\"\u001B[39m\n\u001B[1;32m-> 1305\u001B[0m     \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mKeyboardInterrupt\u001B[39;00m(msg) \u001B[38;5;28;01mfrom\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m\n\u001B[0;32m   1306\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m \u001B[38;5;167;01mException\u001B[39;00m:\n\u001B[0;32m   1307\u001B[0m     \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mlog\u001B[38;5;241m.\u001B[39mwarning(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mInvalid Message:\u001B[39m\u001B[38;5;124m\"\u001B[39m, exc_info\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mTrue\u001B[39;00m)\n",
      "\u001B[1;31mKeyboardInterrupt\u001B[0m: Interrupted by user"
     ]
    }
   ],
   "source": [
    "def stream_graph_updates(user_input: str):\n",
    "    for event in graph.stream({\"messages\": [{\"role\": \"user\", \"content\": user_input}]}):\n",
    "        print(event)\n",
    "        for value in event.values():\n",
    "            print(value)\n",
    "            print(\"Assistant:\", value[\"messages\"][-1].content)\n",
    "\n",
    "while True:\n",
    "    try:\n",
    "        user_input = input(\"User: \")\n",
    "        if user_input.lower() in [\"quit\", \"exit\", \"q\"]:\n",
    "            print(\"Goodbye!\")\n",
    "            break\n",
    "\n",
    "        stream_graph_updates(user_input)\n",
    "    except Exception as e:\n",
    "        print(e)\n",
    "        # fallback if input() is not available\n",
    "        user_input = \"What do you know about LangGraph?\"\n",
    "        print(\"User: \" + user_input)\n",
    "        stream_graph_updates(user_input)\n",
    "        break"
   ]
  },
  {
   "cell_type": "code",
   "id": "58eeb596-299a-47f4-ad79-4f429f2fa3aa",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-08-26T01:43:48.913005Z",
     "start_time": "2025-08-26T01:43:48.614456Z"
    }
   },
   "source": [
    "from typing import Annotated\n",
    "\n",
    "from langchain_tavily import TavilySearch\n",
    "from langchain_core.messages import BaseMessage\n",
    "from typing_extensions import TypedDict\n",
    "\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "from langgraph.graph.message import add_messages\n",
    "from langgraph.prebuilt import ToolNode, tools_condition\n",
    "\n",
    "class State(TypedDict):\n",
    "    messages: Annotated[list, add_messages]\n",
    "\n",
    "graph_builder = StateGraph(State)\n",
    "\n",
    "tool = TavilySearch(max_results=2)\n",
    "tools = [tool]\n",
    "llm_with_tools = llm.bind_tools(tools)\n",
    "\n",
    "def chatbot(state: State):\n",
    "    return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
    "\n",
    "graph_builder.add_node(\"chatbot\", chatbot)\n",
    "\n",
    "tool_node = ToolNode(tools=[tool])\n",
    "graph_builder.add_node(\"tools\", tool_node)\n",
    "\n",
    "graph_builder.add_conditional_edges(\n",
    "    \"chatbot\",\n",
    "    tools_condition,\n",
    ")\n",
    "# Any time a tool is called, we return to the chatbot to decide the next step\n",
    "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
    "graph_builder.add_edge(START, \"chatbot\")\n",
    "graph = graph_builder.compile()\n",
    "# from IPython.display import Image, display\n",
    "# \n",
    "# try:\n",
    "#     display(Image(graph.get_graph().draw_mermaid_png()))\n",
    "# except Exception:\n",
    "#     # This requires some extra dependencies and is optional\n",
    "#     pass\n",
    "# def stream_graph_updates(user_input: str):\n",
    "#     for event in graph.stream({\"messages\": [{\"role\": \"user\", \"content\": user_input}]}):\n",
    "#         print(event)\n",
    "#         for value in event.values():\n",
    "#             print(value)\n",
    "#             print(\"Assistant:\", value[\"messages\"][-1].content)\n",
    "# \n",
    "# while True:\n",
    "#     try:\n",
    "#         user_input = input(\"User: \")\n",
    "#         if user_input.lower() in [\"quit\", \"exit\", \"q\"]:\n",
    "#             print(\"Goodbye!\")\n",
    "#             break\n",
    "# \n",
    "#         stream_graph_updates(user_input)\n",
    "#     except Exception as e:\n",
    "#         print(e)\n",
    "#         # fallback if input() is not available\n",
    "#         user_input = \"What do you know about LangGraph?\"\n",
    "#         print(\"User: \" + user_input)\n",
    "#         stream_graph_updates(user_input)\n",
    "#         break"
   ],
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 10
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d2a8e88a-d177-4750-8ef2-f0973512243e",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "63442519-81a1-4eef-b3d4-33fc21c5785c",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
